<!doctype html>
<html>
<head>
<meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>
<title>项目说明</title></head>
<body><h1>Django花店订单系统</h1>
<h2>1. 项目简介</h2>
<p>这是一个基于Django框架开发的花店订单管理系统，采用前后端不分离的开发模式，使用Django模板引擎进行页面渲染。系统实现了用户管理、商品管理、订单管理等基本功能，适合Django初学者学习和参考。系统采用Bootstrap进行页面美化，实现了响应式设计，同时集成了验证码和邮件功能，提供了完整的用户体验。</p>
<h3>1.1 技术栈</h3>
<ul>
<li>Python 3.10+</li>
<li>Django 5.1.4：Web框架</li>
<li>MySQL 数据库：数据存储</li>
<li>Bootstrap 5.3.0：前端框架</li>
<li>Django Simple Captcha 0.6.0：验证码功能</li>
<li>Pillow：图片处理</li>

</ul>
<h3>1.2 依赖包</h3>
<pre><code>asgiref==3.8.1
Django==5.1.4
django-ranged-response==0.2.0
django-simple-captcha==0.6.0
mysqlclient==2.2.6
pillow==11.0.0
sqlparse==0.5.3
typing_extensions==4.12.2
tzdata==2024.2
</code></pre>
<h2>2. 项目结构</h2>
<pre><code>djangoProject/
├── djangoProject/          # 项目配置目录
│   ├── settings.py        # 项目设置（数据库、邮件等配置）
│   ├── urls.py           # 主URL配置（路由分发）
│   └── wsgi.py           # WSGI配置（部署相关）
├── users/                 # 用户应用
│   ├── views.py          # 用户相关视图（登录、注册等）
│   └── urls.py           # 用户URL配置
├── products/             # 商品应用
│   ├── models.py         # 商品模型定义
│   ├── views.py         # 商品相关视图（列表、详情等）
│   └── urls.py          # 商品URL配置
├── orders/               # 订单应用
│   ├── models.py         # 订单模型定义
│   ├── views.py         # 订单相关视图（创建、查看等）
│   └── urls.py          # 订单URL配置
├── templates/            # 模板目录
│   ├── base.html        # 基础模板（导航栏、页面结构）
│   ├── home.html        # 首页模板（商品展示）
│   ├── users/           # 用户相关模板（登录、注册等）
│   ├── products/        # 商品相关模板（列表、详情）
│   └── orders/          # 订单相关模板（创建、列表）
├── static/              # 静态文件目录（CSS、JS、图片）
└── media/               # 媒体文件目录（上传的商品图片）
</code></pre>
<h2>3. 功能模块</h2>
<h3>3.1 用户模块</h3>
<h4>用户注册：</h4>
<p>使用Django内置的用户模块，也就是继承了Django要求的用户的用户名密码等策略，同时密码也可以自动进行加密存储，提高了系统的安全性，以及后面的用户登录、退出、登录信息持久化存储等也可以方便地完成。在这里我们自定义了一个表单实例CustomUserCreationForm，继承自 <code>UserCreationForm</code>。主要功能包括：</p>
<ol start='' >
<li><strong>添加邮箱字段</strong>：通过扩展 <code>Meta</code> 类，添加了 <code>email</code> 字段，确保用户在注册时必须提供有效的电子邮件地址。</li>
<li><strong>验证邮箱</strong>：在 <code>clean_email</code> 方法中，检查提供的邮箱是否已被其他用户注册。如果已存在，则抛出验证错误</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>def register(request):
    &quot;&quot;&quot;用户注册视图&quot;&quot;&quot;
    if request.method == &#39;POST&#39;:
        form = CustomUserCreationForm(request.POST)     # 使用自己定义的表单实例来验证用户的注册数据
        if form.is_valid():
            user = form.save()
            login(request, user)
            messages.success(request, &#39;注册成功！欢迎加入我们！&#39;)
            return redirect(&#39;login&#39;)
    else:
        form = CustomUserCreationForm()
    return render(request, &#39;users/register.html&#39;, {&#39;form&#39;: form})

class CustomUserCreationForm(UserCreationForm):
    &quot;&quot;&quot;自定义用户注册表单&quot;&quot;&quot;
    email = forms.EmailField(label=&#39;电子邮箱&#39;, required=True, help_text=&#39;请输入有效的电子邮箱地址&#39;)
    
    class Meta(UserCreationForm.Meta):              # 添加一个 email 字段，默认情况下，UserCreationForm 只包含用户名和密码字段。通过扩展 Meta 类并添加 email 字段，可以确保用户在注册时必须提供他们的电子邮件地址。
        fields = UserCreationForm.Meta.fields + (&#39;email&#39;,)

    def clean_email(self):                # 验证邮箱是否已被注册
        email = self.cleaned_data.get(&#39;email&#39;)
        if User.objects.filter(email=email).exists():
            raise forms.ValidationError(&#39;该邮箱已被注册&#39;)
        return email
</code></pre>
<p>相关页面：</p>
<p><img src="项目说明.assets/image-20241215001942234.png" referrerpolicy="no-referrer" alt="image-20241215001942234"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h4>用户登录：</h4>
<p>通过使用验证码来防止非法用户进行密码暴力破解，用户在发起登录请求的时候，使用authenticate()函数来验证用户名和密码是否正确，如果用户名和密码匹配，返回对应的用户对象，否则返回 <code>None</code>。</p>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>def user_login(request):
    &quot;&quot;&quot;用户登录视图&quot;&quot;&quot;
    if request.method == &#39;POST&#39;:
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data.get(&#39;username&#39;)
            password = form.cleaned_data.get(&#39;password&#39;)
            user = authenticate(username=username, password=password)
            if user is not None:
                login(request, user)
                messages.success(request, f&#39;欢迎回来，{username}！&#39;)
                return redirect(&#39;orders:order_list&#39;)
    else:
        form = LoginForm()
    return render(request, &#39;users/login.html&#39;, {&#39;form&#39;: form})
</code></pre>
<p>登录页面</p>
<p><img src="项目说明.assets/image-20241215002401068.png" referrerpolicy="no-referrer" alt="image-20241215002401068"></p>
<h4>用户登出：</h4>
<p>用户在点击退出登录的按钮之后，会退出当前用户，调用Django的logout()函数来执行退出操作，该操作会自动清除掉当前用户登录的本地信息。</p>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>def user_logout(request):
    &quot;&quot;&quot;用户退出登录视图&quot;&quot;&quot;
    logout(request)
    messages.info(request, &#39;您已成功退出登录。&#39;)
    return redirect(&#39;login&#39;)
</code></pre>
<h4>修改密码</h4>
<p>用户在登录之后可以点击导航栏的修改密码按钮进入到修改密码的界面，用户通过输入旧密码可以实现密码的修改。</p>
<p>同时这里也自定了一个CustomPasswordChangeForm来校验用户旧密码是否正确</p>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>class CustomPasswordChangeForm(PasswordChangeForm):
    &quot;&quot;&quot;自定义密码修改表单&quot;&quot;&quot;
    def clean_old_password(self):
        &quot;&quot;&quot;先验证旧密码&quot;&quot;&quot;
        old_password = self.cleaned_data.get(&#39;old_password&#39;)
        if not self.user.check_password(old_password):
            raise forms.ValidationError(&#39;旧密码不正确&#39;)
        return old_password

@login_required
def change_password(request):
    &quot;&quot;&quot;修改密码视图&quot;&quot;&quot;
    if request.method == &#39;POST&#39;:
        form = CustomPasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            messages.success(request, &#39;密码修改成功！&#39;)
            return redirect(&#39;home&#39;)
    else:
        form = CustomPasswordChangeForm(request.user)
    return render(request, &#39;users/change_password.html&#39;, {&#39;form&#39;: form})
</code></pre>
<p>相关视图：</p>
<p><img src="项目说明.assets/image-20241215003357407.png" referrerpolicy="no-referrer" alt="image-20241215003357407"></p>
<h4>找回密码</h4>
<p>用户在忘记密码的情况下，可以向邮箱发送临时密码，用户可以使用该临时密码完成对用户的修改，从而实现找回密码的功能</p>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>def forgot_password(request):
    &quot;&quot;&quot;找回密码视图&quot;&quot;&quot;
    if request.method == &#39;POST&#39;:
        email = request.POST.get(&#39;email&#39;)
        if not email:
            messages.error(request, &#39;请输入邮箱地址&#39;)
            return render(request, &#39;users/forgot_password.html&#39;)
            
        try:
            user = User.objects.get(email=email)
            # 生成临时密码
            temp_password = &#39;&#39;.join(random.choices(string.ascii_letters + string.digits, k=8))
            user.set_password(temp_password)
            user.save()
            
            # 发送临时密码到用户邮箱
            send_mail(
                &#39;密码重置&#39;,
                f&#39;您的临时密码是：{temp_password}，请登录后立即修改密码。&#39;,
                settings.EMAIL_HOST_USER,
                [email],
                fail_silently=False,
            )
            messages.success(request, &#39;临时密码已发送到您的邮箱，请查收。&#39;)
            return redirect(&#39;login&#39;)
        except User.DoesNotExist:
            messages.error(request, &#39;该邮箱未注册，请先注册账号。&#39;)
        except Exception as e:
            messages.error(request, f&#39;发送邮件失败：{str(e)}&#39;)
    return render(request, &#39;users/forgot_password.html&#39;)
</code></pre>
<p>相关视图：</p>
<p><img src="项目说明.assets/image-20241215003551123.png" referrerpolicy="no-referrer" alt="image-20241215003551123"></p>
<h3>3.2 商品模块</h3>
<h4>商品展示：</h4>
<p>商品展示功能主要包括首页展示、商品列表和商品详情三个部分。首页展示采用轮播图形式展示最新的商品，商品列表采用响应式网格布局，商品详情页面展示完整的商品信息。</p>
<ol start='' >
<li><p><strong>首页展示</strong>：</p>
<ul>
<li>使用Django的ORM查询最新的6个商品</li>
<li>使用Bootstrap的轮播组件展示商品图片</li>
<li>显示商品的基本信息（名称、价格）</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>def home(request):
    &quot;&quot;&quot;首页视图&quot;&quot;&quot;
    featured_products = Product.objects.all()[:6]  # 获取最新的6个商品
    return render(request, &#39;home.html&#39;, {&#39;products&#39;: featured_products})
</code></pre>
<p>相关页面：
<img src="项目说明.assets/image-20241215004040112.png" referrerpolicy="no-referrer" alt="image-20241215004040112"></p>
<ol start='2' >
<li><p><strong>商品列表</strong>：</p>
<ul>
<li>使用Bootstrap的网格系统实现响应式布局</li>
<li>每个商品以卡片形式展示</li>
<li>显示商品图片、名称、价格和库存信息</li>
<li>支持分页显示</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>def product_list(request):
    &quot;&quot;&quot;商品列表视图&quot;&quot;&quot;
    query = request.GET.get(&#39;q&#39;, &#39;&#39;)
    products = Product.objects.all()
    
    if query:
        products = products.filter(
            Q(name__icontains=query) |  # 搜索商品名称
            Q(description__icontains=query)  # 搜索商品描述
        )
    
    return render(request, &#39;products/product_list.html&#39;, {
        &#39;products&#39;: products,
        &#39;query&#39;: query
    })
</code></pre>
<p>相关页面：</p>
<p><img src="项目说明.assets/image-20241215004057808.png" referrerpolicy="no-referrer" alt="image-20241215004057808"></p>
<p><img src="项目说明.assets/image-20241215004514958.png" referrerpolicy="no-referrer" alt="image-20241215004514958"></p>
<h4>商品搜索：</h4>
<p>商品搜索功能支持按商品名称和描述进行模糊搜索，采用Django的Q对象实现多条件查询，支持实时搜索结果显示。</p>
<ol start='' >
<li><p><strong>搜索实现</strong>：</p>
<ul>
<li>使用Django的Q对象实现多字段搜索</li>
<li>支持模糊匹配（使用icontains）</li>
<li>搜索结果实时更新</li>
<li>提供清晰的搜索反馈</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'># 在视图中实现搜索逻辑
if query:
    products = products.filter(
        Q(name__icontains=query) |  # 搜索商品名称
        Q(description__icontains=query)  # 搜索商品描述
    )
</code></pre>
<p>相关页面：
<img src="项目说明.assets/image-20241215004141321.png" referrerpolicy="no-referrer" alt="image-20241215004141321"></p>
<h4>商品管理：</h4>
<p>商品管理功能主要通过Django的admin后台实现，包括商品的增删改查、图片上传和库存管理等功能。</p>
<ol start='' >
<li><p><strong>后台管理</strong>：</p>
<ul>
<li>商品的CRUD操作</li>
<li>图片上传和预览</li>
<li>库存管理</li>
<li>批量操作功能</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;商品管理&quot;&quot;&quot;
    list_display = [&#39;name&#39;, &#39;price&#39;, &#39;stock&#39;, &#39;created_at&#39;, &#39;updated_at&#39;]
    list_filter = [&#39;created_at&#39;, &#39;updated_at&#39;]
    search_fields = [&#39;name&#39;, &#39;description&#39;]
    ordering = [&#39;-created_at&#39;]
</code></pre>
<p>相关视图：</p>
<p><img src="项目说明.assets/image-20241215004226857.png" referrerpolicy="no-referrer" alt="image-20241215004226857"></p>
<h3>3.3 订单模块</h3>
<h4>订单创建：</h4>
<p>订单创建功能包括商品数量选择、收货信息填写、库存检查和总价计算等功能。系统会自动检查库存是否充足，并在创建订单时自动更新库存。</p>
<ol start='' >
<li><p><strong>创建流程</strong>：</p>
<ul>
<li>选择商品数量</li>
<li>填写收货地址和联系电话</li>
<li>系统自动检查库存</li>
<li>计算订单总价</li>
<li>创建订单并更新库存</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>@login_required
def create_order(request, product_id):
    &quot;&quot;&quot;创建订单视图&quot;&quot;&quot;
    product = get_object_or_404(Product, pk=product_id)
    
    if request.method == &#39;POST&#39;:
        try:
            quantity = int(request.POST.get(&#39;quantity&#39;, 1))
            if quantity &lt;= 0:
                messages.error(request, &#39;购买数量必须大于0&#39;)
                return redirect(&#39;products:product_detail&#39;, pk=product_id)
            
            if quantity &gt; product.stock:
                messages.error(request, &#39;商品库存不足&#39;)
                return redirect(&#39;products:product_detail&#39;, pk=product_id)
            
            # 创建订单并更新库存
            with transaction.atomic():
                order = Order.objects.create(
                    user=request.user,
                    product=product,
                    quantity=quantity,
                    total_price=product.price * quantity,
                    address=request.POST.get(&#39;address&#39;),
                    phone=request.POST.get(&#39;phone&#39;)
                )
                product.stock -= quantity
                product.save()
                
            messages.success(request, &#39;订单创建成功！&#39;)
            return redirect(&#39;orders:order_list&#39;)
                
        except ValueError:
            messages.error(request, &#39;请输入有效的购买数量&#39;)
    
    return render(request, &#39;orders/create_order.html&#39;, {&#39;product&#39;: product})
</code></pre>
<p>相关页面：
<img src="项目说明.assets/image-20241215004334844.png" referrerpolicy="no-referrer" alt="image-20241215004334844"></p>
<h4>订单管理：</h4>
<p>订单管理功能包括订单列表查看、订单状态更新和订单取消等功能。用户可以查看自己的所有订单，并且可以取消待支付状态的订单。</p>
<ol start='' >
<li><p><strong>订单列表</strong>：</p>
<ul>
<li>分页显示订单信息</li>
<li>显示订单状态和详细信息</li>
<li>支持订单取消操作</li>
<li>订单状态实时更新</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'>@login_required
def order_list(request):
    &quot;&quot;&quot;订单列表视图&quot;&quot;&quot;
    query = request.GET.get(&#39;q&#39;, &#39;&#39;)
    orders = Order.objects.filter(user=request.user)
    
    if query:
        orders = orders.filter(
            Q(product__name__icontains=query) |  # 搜索商品名称
            Q(address__icontains=query) |  # 搜索收货地址
            Q(phone__icontains=query) |  # 搜索联系电话
            Q(status__icontains=query)  # 搜索订单状态
        )
    
    orders = orders.order_by(&#39;-created_at&#39;)
    
    return render(request, &#39;orders/order_list.html&#39;, {
        &#39;orders&#39;: orders,
        &#39;query&#39;: query
    })
</code></pre>
<p>相关页面：
<img src="项目说明.assets/image-20241215004340935.png" referrerpolicy="no-referrer" alt="image-20241215004340935"></p>
<h4>订单搜索：</h4>
<p>订单搜索功能支持多条件搜索，包括商品名称、订单状态、收货地址和联系电话等字段的搜索。</p>
<ol start='' >
<li><p><strong>搜索功能</strong>：</p>
<ul>
<li>支持多字段组合搜索</li>
<li>状态筛选功能</li>
<li>时间排序功能</li>
<li>搜索结果实时更新</li>

</ul>
</li>

</ol>
<p>相关代码：</p>
<pre><code class='language-python' lang='python'># 订单搜索实现
if query:
    orders = orders.filter(
        Q(product__name__icontains=query) |  # 搜索商品名称
        Q(address__icontains=query) |  # 搜索收货地址
        Q(phone__icontains=query) |  # 搜索联系电话
        Q(status__icontains=query)  # 搜索订单状态
    )
</code></pre>
<p>相关页面：
<img src="项目说明.assets/image-20241215004405487.png" referrerpolicy="no-referrer" alt="image-20241215004405487"></p>
<h2>4. 核心模型</h2>
<h3>4.1 商品模型</h3>
<pre><code class='language-python' lang='python'>class Product(models.Model):
    &quot;&quot;&quot;商品模型&quot;&quot;&quot;
    name = models.CharField(&#39;商品名称&#39;, max_length=100)
    description = models.TextField(&#39;商品描述&#39;, blank=True)
    price = models.DecimalField(&#39;价格&#39;, max_digits=10, decimal_places=2)
    stock = models.IntegerField(&#39;库存&#39;, default=0)
    image = models.ImageField(&#39;商品图片&#39;, upload_to=&#39;products/&#39;)
    created_at = models.DateTimeField(&#39;创建时间&#39;, auto_now_add=True)
    updated_at = models.DateTimeField(&#39;更新时间&#39;, auto_now=True)

    class Meta:
        verbose_name = &#39;商品&#39;
        verbose_name_plural = &#39;商品&#39;
        ordering = [&#39;-created_at&#39;]

    def __str__(self):
        return self.name
</code></pre>
<h3>4.2 订单模型</h3>
<pre><code class='language-python' lang='python'>class Order(models.Model):
    &quot;&quot;&quot;订单模型&quot;&quot;&quot;
    STATUS_CHOICES = [
        (&#39;pending&#39;, &#39;待支付&#39;),
        (&#39;paid&#39;, &#39;已支付&#39;),
        (&#39;shipped&#39;, &#39;已发货&#39;),
        (&#39;completed&#39;, &#39;已完成&#39;),
        (&#39;cancelled&#39;, &#39;已取消&#39;),
    ]
    
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=&#39;用户&#39;)
    product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name=&#39;商品&#39;)
    quantity = models.IntegerField(&#39;数量&#39;, validators=[MinValueValidator(1)])
    total_price = models.DecimalField(&#39;总价&#39;, max_digits=10, decimal_places=2)
    status = models.CharField(&#39;订单状态&#39;, max_length=20, choices=STATUS_CHOICES, default=&#39;pending&#39;)
    address = models.TextField(&#39;收货地址&#39;)
    phone = models.CharField(&#39;联系电话&#39;, max_length=20)
    created_at = models.DateTimeField(&#39;创建时间&#39;, auto_now_add=True)
    updated_at = models.DateTimeField(&#39;更新时间&#39;, auto_now=True)

    class Meta:
        verbose_name = &#39;订单&#39;
        verbose_name_plural = &#39;订单&#39;
        ordering = [&#39;-created_at&#39;]

    def __str__(self):
        return f&#39;{self.user.username}的订单 - {self.product.name}&#39;

    def save(self, *args, **kwargs):
        # 自动计算总价
        if not self.total_price:
            self.total_price = self.product.price * self.quantity
        super().save(*args, **kwargs)
</code></pre>
<h2>5. 特色功能</h2>
<h3>5.1 验证码功能</h3>
<ul>
<li><p>登录验证码：</p>
<ul>
<li>使用Django Simple Captcha</li>
<li>自动刷新机制</li>
<li>验证码图片美化</li>
<li>错误提示功能</li>

</ul>
</li>

</ul>
<h3>5.2 邮件功能</h3>
<ul>
<li><p>密码找回：</p>
<ul>
<li>使用QQ邮箱SMTP服务</li>
<li>HTML格式邮件</li>
<li>临时密码生成</li>
<li>发送失败处理</li>

</ul>
</li>

</ul>
<h3>5.3 页面美化</h3>
<ul>
<li><p>Bootstrap界面：</p>
<ul>
<li>响应式导航栏</li>
<li>卡片式布局</li>
<li>表单样式优化</li>
<li>移动端适配</li>

</ul>
</li>

</ul>
<p>&nbsp;</p>
<p>&nbsp;</p>
</body>
</html>